//
// Created by mmuee on 2024/5/24.
//

#include "URI.hpp"
#include <cstdlib>
#include <string.h>
#include <algorithm>
#include <sstream>

namespace utils
{
    class Parser
    {
    public:
        // check if the scheme name is valid
        static bool isSchemeValid(const std::string& schemeName)
        {
            for (const auto& c : schemeName)
            {
                if (!isalpha(c) && c != '+' && c != '-' && c != '.')
                    return false;
            }
            return true;
        }

        /// parse the URL
        static URI::Error parseURL(const std::string& uri, URI& result)
        {
            auto e = _parseURL(uri, result);
            if (result.scheme == "file" && !result.host.empty())
            {
                result.path = result.host + result.path;
                result.host = "";
            }
            return e;
        };

        static URI::Error _parseURL(const std::string& uri, URI& result)
        {
            const char* currentString = uri.c_str();

            // try to read scheme
            {
                const char* localString = strchr(currentString, ':');

                if (!localString)
                {
                    return (URI::NoUrlCharacter);
                }

                // save the scheme name
                result.scheme = std::string(currentString, localString - currentString);

                if (!isSchemeValid(result.scheme))
                {
                    return (URI::InvalidSchemeName);
                }

                // scheme should be lowercase
                std::transform(result.scheme.begin(), result.scheme.end(), result.scheme.begin(), ::tolower);

                // skip ':'
                currentString = localString + 1;
            }

            /*
             *	//<user>:<password>@<host>:<port>/<url-path>
             *	any ":", "@" and "/" must be normalized
             */

            // skip "//"
            if (*currentString++ != '/')
                return (URI::NoDoubleSlash);
            if (*currentString++ != '/')
                return (URI::NoDoubleSlash);

            bool bHasUserName = false;

            const char* localString = currentString;

            while (*localString)
            {
                if (*localString == '@')
                {
                    // user name and password are specified
                    bHasUserName = true;
                    break;
                }
                else if (*localString == '/')
                {
                    // end of <host>:<port> specification
                    bHasUserName = false;
                    break;
                }

                localString++;
            }

            // user name and password
            localString = currentString;

            if (bHasUserName)
            {
                // read user name
                while (*localString && *localString != ':' && *localString != '@')
                    localString++;

                result.username = std::string(currentString, localString - currentString);

                // proceed with the current pointer
                currentString = localString;

                if (*currentString == ':')
                {
                    // skip ':'
                    currentString++;

                    // read password
                    localString = currentString;

                    while (*localString && *localString != '@')
                        localString++;

                    result.password = std::string(currentString, localString - currentString);

                    currentString = localString;
                }

                // skip '@'
                if (*currentString != '@')
                {
                    return (URI::NoAtSign);
                }

                currentString++;
            }

            const bool bHasBracket = (*currentString == '[');

            // go ahead, read the host name
            localString = currentString;

            while (*localString)
            {
                if (bHasBracket && *localString == ']')
                {
                    // end of IPv6 address
                    localString++;
                    break;
                }
                else if (!bHasBracket && (*localString == ':' || *localString == '/'))
                {
                    // port number is specified
                    break;
                }

                localString++;
            }

            result.host = std::string(currentString, localString - currentString);

            currentString = localString;

            // is port number specified?
            if (*currentString == ':')
            {
                currentString++;

                // read port number
                localString = currentString;

                while (*localString && *localString != '/')
                    localString++;

                auto portStr = std::string(currentString, localString - currentString);
                result.port = uint16_t(std::atoi(portStr.c_str()));

                currentString = localString;
            }

            // end of string
            if (!*currentString)
            {
                result.error = URI::Ok;
                return URI::Ok;
            }

            // skip '/'
            if (*currentString != '/')
            {
                return (URI::NoSlash);
            }
            //currentString++;

            // parse the path
            localString = currentString;

            while (*localString && *localString != '#' && *localString != '?')
                localString++;

            result.path = std::string(currentString, localString - currentString);

            currentString = localString;

            // check for query
            if (*currentString == '?')
            {
                // skip '?'
                currentString++;

                // read query
                localString = currentString;

                while (*localString && *localString != '#')
                    localString++;

                result.query = std::string(currentString, localString - currentString);

                currentString = localString;
            }

            // check for fragment
            if (*currentString == '#')
            {
                // skip '#'
                currentString++;

                // read fragment
                localString = currentString;

                while (*localString)
                    localString++;

                result.fragment = std::string(currentString, localString - currentString);

                currentString = localString;
            }

            result.error = URI::Ok;
            return URI::Ok;
        }
    };


    URI::URI(const std::string& uri)
    {
        Parser::parseURL(uri, *this);
    }

    URI URI::file(const std::string& path)
    {
        std::stringstream sb{};
        static const char* fileScheme = "file://";
        if (path.substr(0, 7) != fileScheme)
        {
            sb << fileScheme;
        }
        sb << path;
        return URI(sb.str());
    }

    URI URI::fromPlatform(const std::string& uri)
    {
        auto u = URI(uri);
        return u;
    }

    std::string URI::toPlatform() const
    {
        auto str = toString();
        return str;
    }

    std::string URI::toString() const
    {
        std::stringstream ss{};
        ss << scheme << "://";
        if (!username.empty() && !password.empty())
        {
            ss << username << ":" << password << "@";
        }
        ss << host;
        if (port > 0)
        {
            ss << ":" << port;
        }
        ss << path;
        if (!query.empty())
        {
            ss << "?" << query;
        }
        if (!fragment.empty())
        {
            ss << "#" << fragment;
        }
        return ss.str();
    }

} // namespace utils
